package aggregation.simulator.snapshot;

import java.util.ArrayList;
import java.util.HashMap;

import common.peer.PeerAddress;
import java.util.HashSet;
import java.util.Set;

public class Snapshot {

    private static HashMap<PeerAddress, PeerInfo> peers = new HashMap<PeerAddress, PeerInfo>();
    private static int counter = 0;
    private static String FILENAME = "aggregation_graph2_alg2.dat";    //save data for graph 3 (task 1) to aggregation_graph3.dat instead
    private static double realAverageBw;
    private static final double ERROR_BOUND = 0.1;

//-------------------------------------------------------------------
    public static void init(int numOfStripes) {
        FileIO.write("", FILENAME);
        realAverageBw = 1;
    }

//-------------------------------------------------------------------
    public static void addPeer(PeerAddress address) {
        peers.put(address, new PeerInfo());
        
    }

//-------------------------------------------------------------------
    public static void removePeer(PeerAddress address) {
        peers.remove(address);
        
    }

//-------------------------------------------------------------------
    public static void updateNum(PeerAddress address, double num) {
        PeerInfo peerInfo = peers.get(address);

        if (peerInfo == null) {
            return;
        }

        peerInfo.updateNum(num);
        //update the real average bandwidth
        updateRealAverageBw();

    }

    public static void updateEstimatedBw(PeerAddress address, double estimatedBw) {
        PeerInfo peerInfo = peers.get(address);

        if (peerInfo == null) {
            return;
        }

        peerInfo.updateEstimatedBw(estimatedBw);

    }

//-------------------------------------------------------------------
    public static void updateCyclonPartners(PeerAddress address, ArrayList<PeerAddress> partners) {
        PeerInfo peerInfo = peers.get(address);

        if (peerInfo == null) {
            return;
        }

        peerInfo.updateCyclonPartners(partners);
    }

//-------------------------------------------------------------------
    public static void report() {
        /*if (counter ==5){
        Set<PeerAddress> peersToRemove = new HashSet<PeerAddress>();
        for (PeerAddress peer: peers.keySet()){
        if (peers.get(peer).getCyclonPartners().isEmpty())
        peersToRemove.add(peer);
        }
        
        for (PeerAddress peer: peersToRemove){
        System.out.println("Peer "+peer+" removed, cyclonpartners: "+peers.get(peer).getCyclonPartners());
        peers.remove(peer);
        }
        
        realAverageBw = 0;
        for (PeerAddress peer : peers.keySet()) {
        realAverageBw += peers.get(peer).getNum();
        System.out.println("Peer "+peer+" my bandwidth is: "+peers.get(peer).getNum());
        
        }
        
        realAverageBw /= peers.size();
        System.out.println("New realAverageBw is: "+realAverageBw);
        }*/


        /*
        String str = new String();
        if (counter % 101 == 0) {
        str += "current time: " + counter++ + "\n";
        str += reportNetworkState();
        //str += verifyNetworkSize();
        str += "Real Average Bandwidth: " + realAverageBw + "\n";
        str += verifyAverageBandwidth();
        str += reportDetailes();
        
        str += "###\n";
        
        //Print data for graphs 1 and 2 (Task 1)
        //str += counter + "\t" + peers.size() + "\t" + realAverageBw + "\t" + verifyAverageBandwidth();
        
        //Print data for graph 3 (Task 1)
        //First set the following: '(counter % 120 == 0)' and 'if (counter == 1) {System.exit(0);'
        str += verifyAverageBandwidth();
        }*/

        String str = "";
        //str+="current time: "+counter+"\n";
        //str+=reportNetworkState();
        //str+=verifyAverageBandwidth();
        //str+=reportDetailes();        
        str += plotGraph2();

        System.out.print(str);
        FileIO.append(str, FILENAME);

        counter++;


    }

    private static String plotGraph1() {
        String str = "";
        if (counter == 0) {
            str += "Aggregation Graph1\n";
            str += "round\tnumber of nodes error%<10%\n";

        }

        if (counter % 20 == 0) {

            PeerInfo peerInfo;
            int correct = 0;

            for (PeerAddress peer : peers.keySet()) {
                peerInfo = peers.get(peer);
                double estimated = peerInfo.getEstimatedBw();
                if (Math.abs(estimated - realAverageBw) <= realAverageBw * ERROR_BOUND) {
                    correct++;
                }
            }
            double correctPerc = (double) correct / peers.size();
            str += counter + "\t" + correctPerc + "\n";

        }
        return str;
    }

    private static String plotGraph2() {
        String str = "";
        if (counter == 0) {
            str += "Aggregation Graph2\n";
            str += "round\taverage st dev\n";
        }


        PeerInfo peerInfo;
        
        double stDev=0;
        for (PeerAddress peer : peers.keySet()) {
            peerInfo = peers.get(peer);
            stDev+= Math.pow(peerInfo.getEstimatedBw()-realAverageBw,2); 
            
        }
        stDev=Math.sqrt(stDev/(peers.size()-1));
        
        str += counter + "\t" + stDev + "\n";

        return str;
    }

    private static String plotGraph3() {
        String str = "";
        if (counter == 0) {
            str += "Aggregation Graph3\n";

        }

        if (counter == 100) {
            str += "RealAvergaeBw: " + realAverageBw + "\n";
            str += "peerId\testimatedBw\n";
            for (PeerAddress peer : peers.keySet()) {
                PeerInfo info = peers.get(peer);
                str += peer.getPeerId().intValue();
                str += "\t";
                str += info.getEstimatedBw();
                str += "\n";
            }
        }
        return str;

    }

//-------------------------------------------------------------------
    private static String reportNetworkState() {
        //String str = new String("---\n");
        String str = "";
        int totalNumOfPeers = peers.size();
        str += "total number of peers: " + totalNumOfPeers + "\n";

        return str;
    }

//-------------------------------------------------------------------
    private static String reportDetailes() {
        PeerInfo peerInfo;
        String str = new String("---\n");

        for (PeerAddress peer : peers.keySet()) {
            peerInfo = peers.get(peer);

            str += "peer: " + peer;
            str += ", cyclon partners: " + peerInfo.getCyclonPartners();
            str += "\n";
        }

        return str;
    }

//-------------------------------------------------------------------
    private static String verifyNetworkSize() {
        PeerInfo peerInfo;
        int correct = 0;
        double estimated = 0;


        String str = new String("---\n");

        for (PeerAddress peer : peers.keySet()) {
            peerInfo = peers.get(peer);
            estimated = 1 / peerInfo.getNum();
            str += peer + " --> estimated size: " + estimated + "\n";
            if (Math.abs(estimated - peers.size()) <= peers.hashCode() * 0.02) {
                correct++;
            }
        }

        str += "estimated correctly: " + correct + "\n";
        return str;
    }

    private static String verifyAverageBandwidth() {
        PeerInfo peerInfo;
        int correct = 0;

        double averageEstimated = 0;
        String str = new String("---\n");
        //String str = "";
        for (PeerAddress peer : peers.keySet()) {
            peerInfo = peers.get(peer);
            double estimated = peerInfo.getEstimatedBw();
            averageEstimated += estimated;
            str += peer + " --> estimated size: " + estimated + "\n";

            //Print data for graph 3 (Task 1)
            str += peer + "\t" + estimated + "\n";

            if (Math.abs(estimated - realAverageBw) <= realAverageBw * ERROR_BOUND) {
                correct++;
            }
        }
        averageEstimated /= peers.size();
        str += "real average bw: " + realAverageBw + "\n";
        str += "error bound in percentage: " + ERROR_BOUND + "\n";
        str += "estimated correctly: " + correct + "\n";

        //Compute the fraction of nodes that estimated the average bandwidth correctly 
        double correct_double = correct;
        double correct_percentage = correct_double / peers.size();
        str += "estimated correctly in percentage: " + correct_percentage + "\n";

        str += "average estimated bandwidth: " + averageEstimated + "\n";

        //Compute the standard deviation of the average estimated bandwidth
        double standardDeviation = Math.abs(averageEstimated - realAverageBw);
        str += "SD of average estimated bandwidth: " + standardDeviation + "\n";

        //Print data for graphs 1 and 2 (Task 1)
        /*str += ERROR_BOUND + "\t" + correct + "\t" + correct_percentage + "\t" +
        + averageEstimated + "\t" + standardDeviation + "\n";*/

        return str;
    }

    private static void updateRealAverageBw() {
        realAverageBw = 0;
        for (PeerAddress peer : peers.keySet()) {
            realAverageBw += peers.get(peer).getNum();
        }

        realAverageBw /= peers.size();
    }
}
